home *** CD-ROM | disk | FTP | other *** search
- /*
- StringBounds.c
-
- void GetBounds(GWorldPtr window,Rect *rectPtr,Rect *boundsPtr,long *inkAreaPtr);
- Return the smallest bounding box containing all nonzero pixels, and a count of the nonzero pixels.
-
- void StrBounds(char *str,Rect *bounds,long *inkArea);
- void StringBounds(const unsigned char *string,Rect *bounds,long *inkArea);
- StrBounds accepts a C string. StringBounds accepts a pascal string.
- Both routines count--and compute the minimum bounding rectangle for--all the pixels that
- would be set black by calling DrawString with the given string with the
- current port's font, size, and style. Coordinates in the *bounds rect are
- relative to the current pen position. If no pixels would be set then *bounds is
- set to the empty rect (0,0,0,0). The current port is left untouched.
-
- The measurements are based on a full rendering of the string in a private
- GWorld, without any clipping.
-
- The bounds and inkArea arguments are optional; they may be replaced by NULL.
-
- void CharBounds(char a,Rect *bounds,long *inkArea);
- Creates a one-char string and calls StringBounds().
-
- double StrOutlineLength(char *s);
- double StringOutlineLength(const unsigned char *s);
- Measure the outline length of a string, in pixels. Formerly (before 2/20/95)
- I thought it was enough to just count the number of pixels in the 1-pixel thick
- outline that one obtains when drawing a PostScript or TrueType character in
- outline style. However, I later discovered that this only gives correct results
- for vertical and horizontal segments of the the outline. Diagonal segments of
- the outline are, necessarily, a stairstep, so a pixel count yields a city-block
- path length. For a 45 deg slope the pixel count is too high by a factor of
- square root of 2, i.e. by roughly a factor of 1.5. StringOutlineLength() tries
- to provide a more accurate estimate of outline length. It creates the 1-pixel
- outline and then thickens it by repeatedly ORing the image with itself, shifted
- by one pixel: left, right, up, and down. This has the effect of tripling the
- thickness of horizontal and vertical lines, from 1 to 3 pixels, and doubling the
- thickness of 45 deg diagonal lines. Since the diagonal lines were already
- providing about 1.5 times as many pixels as the true length, when doubled, they
- provide triple the length, just like the horizontal and vertical lines. Thus, to
- a first order, the length measurement should be insensitive to orientation of
- the segments of the outline.
-
- NOTES
-
- bounds->right need not exactly equal the value returned by StringWidth(s),
- because StringWidth returns the displacement of the pen position, and some (e.g.
- italic) characters extend beyond that, while others (e.g. comma) extend less
- far. Similarly, bounds->left need not always be zero.
-
- By QuickDraw's convention, each pixel is considered to lie below and to the
- right of the point that is used to address it. The bounding rectangle for a
- pixel at x,y, is SetRect(&r,x,y,x+1,y+1).
-
- The code assumes ForeColor is black and that TextMode is srcOr, which are the
- default settings for the GWorld that we create.
-
- HISTORY:
- 1/17/94 dgp wrote it, based on discussion with Bart Farell and Manoj Gunwani.
- 2/22/94 dgp added inkArea argument, and made both bounds and inkArea optional.
- 2/27/94 dgp since this routine is typically called repeatedly I save time
- (0.2 s per call) by only allocating the GWorld once, and never
- freeing it. It is reused on subsequent calls, resized appropriately
- if necessary.
- 9/5/94 dgp removed assumption in printf's that int==short.
- 11/2/94 dgp discovered that the clip rect may be nonsense after calling
- UpdateGWorld, so I set it equal to the portRect.
- 2/20/95 dgp Added StringOutlineLength and StrOutlineLength.
- 2/24/95 dgp Fixed above to give correct length even when stroke is only one pixel thick.
- */
- #include "VideoToolbox.h"
- #define SHOW_BITMAPS 0 // for debugging
-
- double StrOutlineLength(char *s);
- double StringOutlineLength(const unsigned char *s);
-
- void GetBounds(GWorldPtr window,Rect *rectPtr,Rect *boundsPtr,long *inkAreaPtr);
-
- void GetBounds(GWorldPtr window,Rect *rectPtr,Rect *boundsPtr,long *inkAreaPtr)
- {
- Rect r,bounds;
- GWorldPtr oldPort;
- GDHandle oldDevice;
- register unsigned long *pix;
- register int x,y;
- register long inkArea=0;
- long n;
-
- // measure the bounding box, and count the black pixels
- GetGWorld(&oldPort,&oldDevice);
- assert(window!=NULL);
- SectRect(&window->portRect,rectPtr,&r);
- SetRect(&bounds,r.right,r.bottom,r.left,r.top);
- n=r.right-r.left;
- pix=(unsigned long *)NewPtr(n*sizeof(*pix));
- if(pix==NULL)PrintfExit("%s line %d: Couldn't allocate %ld bytes.\n"
- ,__FILE__,__LINE__,n*sizeof(long));
- if(IsGrafPtr(window))SetPort((WindowPtr)window);
- else SetGWorld(window,GetMainDevice());
- OffsetRect(&bounds,-r.left,-r.top);
- for(y=0;y<r.bottom-r.top;y++){
- GetPixelsQuickly(r.left,r.top+y,pix,n);
- for(x=n-1;x>=0;x--)if(pix[x]!=0){
- inkArea++;
- if(x<bounds.left)bounds.left=x;
- if(x>=bounds.right)bounds.right=x+1;
- if(y<bounds.top)bounds.top=y;
- if(y>=bounds.bottom)bounds.bottom=y+1;
- }
- }
- SetGWorld(oldPort,oldDevice);
- DisposePtr((void *)pix);
- OffsetRect(&bounds,r.left,r.top);
- if(EmptyRect(&bounds))SetRect(&bounds,0,0,0,0);
- if(boundsPtr!=NULL)*boundsPtr=bounds;
- if(inkAreaPtr!=NULL)*inkAreaPtr=inkArea;
- }
-
- void CharBounds(char a,Rect *boundsPtr,long *inkAreaPtr)
- {
- unsigned char string[]="\pA";
-
- string[1]=a;
- StringBounds(string,boundsPtr,inkAreaPtr);
- }
-
- void StrBounds(char *s,Rect *boundsPtr,long *inkAreaPtr)
- {
- StringBounds(c2pstr(s),boundsPtr,inkAreaPtr);
- p2cstr((unsigned char *)s);
- }
-
- void StringBounds(const unsigned char *s,Rect *boundsPtr,long *inkAreaPtr)
- {
- static GWorldPtr our=NULL;
- GWorldPtr old;
- GDHandle oldDevice;
- FontInfo f;
- Rect r;
- int error;
- char string[40];
-
- assert(StackSpace()>4000);
- GetFontInfo(&f);
- SetRect(&r,0,-f.ascent,StringWidth(s),f.descent); // nominal size
- InsetRect(&r,-(f.widMax+2),-(f.leading+2)); // add margin
-
- // draw string into a new GWorld
- if(our!=NULL){
- DisposeGWorld(our);
- our=NULL;
- }
- if(our==NULL){
- error=NewGWorld(&our,1,&r,NULL,NULL,keepLocal|useTempMem);
- if(error)error=NewGWorld(&our,1,&r,NULL,NULL,keepLocal);
- }else{
- error=UpdateGWorld(&our,1,&r,NULL,NULL,clipPix);
- assert(EqualRect(&r,&our->portRect));
- assert((**our->clipRgn).rgnSize==10);
- (**our->clipRgn).rgnBBox=our->portRect;
- }
- if(error)PrintfExit("StringBounds: NewGWorld/UpdateGWorld error %d.\n",error);
- GetGWorld(&old,&oldDevice);
- SetGWorld(our,NULL);
- TextFace(old->txFace);
- TextFont(old->txFont);
- TextSize(old->txSize);
- EraseRect(&our->portRect);
- MoveTo(0,0);
- DrawString(s);
- SetGWorld(old,oldDevice);
- if(SHOW_BITMAPS){
- PrintfGWorld(our);
- gets(string);
- }
-
- GetBounds(our,&our->portRect,boundsPtr,inkAreaPtr);
-
- if(SHOW_BITMAPS){
- PrintfGWorld(our);
- gets(string);
- }
- if(0){
- DisposeGWorld(our);
- our=NULL;
- }
- }
-
- double StrOutlineLength(char *s)
- {
- double length;
-
- length=StringOutlineLength(c2pstr(s));
- p2cstr((unsigned char *)s);
- return length;
- }
-
- //#undef SHOW_BITMAPS
- //#define SHOW_BITMAPS 1
-
- double StringOutlineLength(const unsigned char *s)
- {
- static GWorldPtr world=NULL;
- GWorldPtr old;
- GDHandle oldDevice;
- FontInfo f;
- Rect r,rSrc,rDst,rSmall;
- register unsigned long *pix;
- register int x,y;
- register long inkArea=0;
- int n;
- int error;
- char string[40];
-
- assert(StackSpace()>4000);
- GetFontInfo(&f);
- SetRect(&rSmall,0,-f.ascent,StringWidth(s),f.descent); // nominal size
- InsetRect(&rSmall,-(f.widMax+4),-(f.leading+4)); // add margin
- rSrc=rSmall;
- rSrc.top*=2; // allow room for double-size text
- rSrc.left*=2;
- rSrc.bottom*=2;
- rSrc.right*=2;
- r=rSrc;
- r.right+=r.right-r.left; // double rect, to make room for rDst.
- rDst=rSrc;
- OffsetRect(&rDst,rSrc.right-rSrc.left,0);
- InsetRect(&rSrc,1,1);
- InsetRect(&rDst,1,1);
-
- // draw string into a new GWorld
- if(world!=NULL){
- DisposeGWorld(world);
- world=NULL;
- }
- if(world==NULL){
- error=NewGWorld(&world,1,&r,NULL,NULL,keepLocal|useTempMem);
- if(error)error=NewGWorld(&world,1,&r,NULL,NULL,keepLocal);
- }else{
- error=UpdateGWorld(&world,1,&r,NULL,NULL,clipPix);
- assert(EqualRect(&r,&world->portRect));
- assert((**world->clipRgn).rgnSize==10);
- (**world->clipRgn).rgnBBox=world->portRect;
- }
- if(error)PrintfExit("StringOutlineLength: NewGWorld/UpdateGWorld error %d.\n",error);
- GetGWorld(&old,&oldDevice);
- SetGWorld(world,NULL);
- TextFace(old->txFace);
- TextFont(old->txFont);
- TextSize(old->txSize);
- EraseRect(&world->portRect);
- MoveTo(0,0);
- DrawString(s);
- SetGWorld(old,oldDevice);
-
- // Expand text so that all features are at least 2 pixels thick
- error=CopyWindows(world,world,&rSmall,&rDst,srcCopy,NULL);
- error=CopyWindows(world,world,&rDst,&rSrc,srcCopy,NULL);
-
- // Grow each black pixel into a 3x3 square.
- r=rDst;
- OffsetRect(&r,0,1);
- error=CopyWindows(world,world,&rSrc,&r,srcOr,NULL);
- OffsetRect(&r,0,-2);
- error=CopyWindows(world,world,&rSrc,&r,srcOr,NULL);
- OffsetRect(&r,1,1);
- error=CopyWindows(world,world,&rDst,&r,srcOr,NULL);
- OffsetRect(&r,-2,0);
- error=CopyWindows(world,world,&rDst,&r,srcOr,NULL);
-
- // Clear the pixels corresponding to the original image, leaving a 1 pixel outline.
- error=CopyWindows(world,world,&rSrc,&rDst,srcBic,NULL);
- error=CopyWindows(world,world,&rDst,&rSrc,srcCopy,NULL);
-
- // Grow each black pixel, so that lines of all orientations are effectively 3 pixels thick.
- r=rDst;
- OffsetRect(&r,0,1);
- error=CopyWindows(world,world,&rSrc,&r,srcOr,NULL);
- OffsetRect(&r,0,-2);
- error=CopyWindows(world,world,&rSrc,&r,srcOr,NULL);
- OffsetRect(&r,1,1);
- error=CopyWindows(world,world,&rSrc,&r,srcOr,NULL);
- OffsetRect(&r,-2,0);
- error=CopyWindows(world,world,&rSrc,&r,srcOr,NULL);
-
- // count the black pixels
- r=rDst;
- n=r.right-r.left;
- pix=(unsigned long *)NewPtr(n*sizeof(*pix));
- if(pix==NULL)PrintfExit("%s line %d: Couldn't allocate %ld bytes.\n"
- ,__FILE__,__LINE__,n*sizeof(long));
- SetGWorld(world,NULL);
- for(y=0;y<r.bottom-r.top;y++){
- GetPixelsQuickly(r.left,r.top+y,pix,n);
- for(x=n-1;x>=0;x--)if(pix[x]!=0){
- inkArea++;
- }
- }
- if(SHOW_BITMAPS){
- PrintfGWorld(world);
- gets(string);
- }
- SetGWorld(old,oldDevice);
- DisposePtr((void *)pix);
- if(0){
- DisposeGWorld(world);
- world=NULL;
- }
- return inkArea/6.0;
- }
-